#include <ctype.h>
#include <dirent.h>
#include <regex.h>
#include <stdlib.h>
#include <string.h>

#include "main.h"

#define DIR_MAX 254

extern Z80EX_BYTE memory[MEMORY_SIZE];
Z80EX_WORD dma_address = 0x80;

typedef struct {
	Z80EX_BYTE drive;
	Z80EX_BYTE filename[8];
	Z80EX_BYTE filetype[3];
	Z80EX_BYTE ex;
	Z80EX_BYTE s1;
	Z80EX_BYTE s2;
	Z80EX_BYTE rc;
	Z80EX_BYTE al[16];
	Z80EX_BYTE cr;
	Z80EX_BYTE r[3];
} file_control_block;

char* directory[DIR_MAX]; // names in upper case
char* directory_original[DIR_MAX]; // names in original case
FILE* opened_files[DIR_MAX];

void get_file_name(file_control_block* fcb, char* buf)
{
	int p = 0;
	int i;
	for (i = 0; i < 8; i++)
		if (fcb->filename[i] != 0x20)
			buf[p++] = fcb->filename[i];
		else
			break;
	buf[p++] = '.';
	for (i = 0; i < 3; i++)
		if (fcb->filetype[i] != 0x20)
			buf[p++] = fcb->filetype[i];
	buf[p] = 0;
}

void get_directory()
{
	const char* regexp_string = "^[a-zA-Z0-9\\$\\-_]{1,8}\\.[a-zA-Z0-9\\$\\-_]{1,3}$";
	regex_t regexp;
	if (regcomp(&regexp, regexp_string, REG_EXTENDED | REG_NOSUB) != 0)
	{
		printf("Error while regexp compiling");
	}
	
	DIR *dp;
	struct dirent *ep;     
	dp = opendir ("./");

	if (dp != NULL)
	{
		int dir_entry_count = 0;
		
		while ((ep = readdir(dp)) && (dir_entry_count < DIR_MAX))
		{
			if (regexec(&regexp, ep->d_name, 0, NULL, 0) == 0)
			{
				int length = strlen(ep->d_name);
				directory[dir_entry_count] = malloc(length + 1);
				directory_original[dir_entry_count] = malloc(length + 1);
				int i;
				for (i = 0; i < length; i++)
				{
					directory[dir_entry_count][i] = toupper(ep->d_name[i]);
					directory_original[dir_entry_count][i] = ep->d_name[i];
				}
				directory[dir_entry_count][i] = 0;
				directory_original[dir_entry_count][i] = 0;
				dir_entry_count++;
			}
		}
	  closedir(dp);
	}
}

int get_file_directory_position(file_control_block* fcb)
{
	char filename[13];
	get_file_name(fcb, filename);
	int p = 0;
	while(directory[p] != NULL && p < DIR_MAX)
	{
		if (strcmp(directory[p], filename) == 0)
		{
			return p;
		}
		p++;
	}
	return -1;
}

/* Entered with C=0Dh. Returned values A=0xFF is $$$.SUB exists. A=0 otherwise */
void reset_discs(Z80EX_CONTEXT *cpu, Z80EX_BYTE c, Z80EX_WORD de)
{
	z80_set_high(cpu, regAF, 0);
}

/* Entered with C=0Eh, E=drive number. Returns L=A=0 (if successful) or 0FFh. */
void select_disc(Z80EX_CONTEXT *cpu, Z80EX_BYTE c, Z80EX_WORD de)
{
	z80_set_high(cpu, regAF, 0);
	z80_set_low(cpu, regHL, 0);
}

/* Entered with C=0Fh, DE=FCB address. Returns error codes in BA and HL. */
void open_file(Z80EX_CONTEXT *cpu, Z80EX_BYTE c, Z80EX_WORD de)
{
	file_control_block* fcb = (file_control_block*)(&memory[de]);
	char filename[13];
	get_file_name(fcb, filename);

	int p = get_file_directory_position(fcb);
	if (p != -1)
	{
		opened_files[p] = fopen(directory_original[p], "r+");
		z80_set_high(cpu, regAF, 0x00); // success
	}
	else
	{
		z80_set_high(cpu, regAF, 0xFF); // error
	}
}

/* Entered with C=10h, DE=FCB address. Returns error codes in BA and HL. */
void close_file(Z80EX_CONTEXT *cpu, Z80EX_BYTE c, Z80EX_WORD de)
{
	file_control_block* fcb = (file_control_block*)(&memory[de]);
	int p = get_file_directory_position(fcb);
	
	if (p != -1)
	{
		if (opened_files[p] != 0)
		{
			fclose(opened_files[p]);
			opened_files[p] = 0;
		}
		z80_set_high(cpu, regAF, 0x00); // success
	}
	else
		z80_set_high(cpu, regAF, 0xFF); // error
}

/* Entered with C=11h, DE=address of FCB. Returns error codes in BA and HL. */
void search_for_first(Z80EX_CONTEXT *cpu, Z80EX_BYTE c, Z80EX_WORD de)
{
	printf("search_for_first\n");
}

/* Entered with C=12h, (DE=address of FCB)?. Returns error codes in BA and HL. */
void search_for_next(Z80EX_CONTEXT *cpu, Z80EX_BYTE c, Z80EX_WORD de)
{
	printf("search_for_next\n");
}

/* Entered with C=13h, DE=address of FCB. Returns error codes in BA and HL. */
void delete_file(Z80EX_CONTEXT *cpu, Z80EX_BYTE c, Z80EX_WORD de)
{
	// TODO: delete using mask
	file_control_block* fcb = (file_control_block*)(&memory[de]);
	int p = get_file_directory_position(fcb);
	if (p != -1)
	{
		char filename[13];
		get_file_name(fcb, filename);

		if (opened_files[p] != 0)
		{
			fflush(opened_files[p]);
			fclose(opened_files[p]);
			opened_files[p] = 0;
		}

		int last = 0;
		while (directory[last] != NULL) last++;
		last--;
		directory[p] = directory[last];
		directory_original[p] = directory_original[last];
		opened_files[p] = opened_files[last];
		
		directory[last] = NULL;
		directory_original[last] = NULL;
		opened_files[last] = NULL;

		remove(filename);
		z80_set_high(cpu, regAF, 0x00); // success
	}
	else
		z80_set_high(cpu, regAF, 0xFF); // error
}

/* Entered with C=14h, DE=address of FCB. Returns error codes in BA and HL. */
void read_next_record(Z80EX_CONTEXT *cpu, Z80EX_BYTE c, Z80EX_WORD de)
{
	file_control_block* fcb = (file_control_block*)(&memory[de]);
	int p = get_file_directory_position(fcb);

	if (p != -1 && opened_files[p] != 0)
	{
		int b = fread(&memory[dma_address], 1, 128, opened_files[p]);
		if (b == 0)
		{
			z80_set_high(cpu, regAF, 0x01); // EOF
		}
		else
		{
			z80_set_high(cpu, regAF, 0x00); // Success
		}
	}
	else
		z80_set_high(cpu, regAF, 0x09); // Invalid FCB
}

/* Entered with C=15h, DE=address of FCB. Returns error codes in BA and HL. */
void write_next_record(Z80EX_CONTEXT *cpu, Z80EX_BYTE c, Z80EX_WORD de)
{
	file_control_block* fcb = (file_control_block*)(&memory[de]);
	int p = get_file_directory_position(fcb);
	
	if (p != -1 && opened_files[p] != 0)
	{
		int b = fwrite(&memory[dma_address], 1, 128, opened_files[p]);
		if (b == 0)
		{
			z80_set_high(cpu, regAF, 0x02); // Disk full
		}
		else
		{
			z80_set_high(cpu, regAF, 0x00); // Success
		}
	}
	else
		z80_set_high(cpu, regAF, 0x09); // Invalid FCB
}

/* Entered with C=16h, DE=address of FCB. Returns error codes in BA and HL. */
void create_file(Z80EX_CONTEXT *cpu, Z80EX_BYTE c, Z80EX_WORD de)
{
	file_control_block* fcb = (file_control_block*)(&memory[de]);
	char filename[13];
	get_file_name(fcb, filename);
	FILE* f = fopen(filename, "w");
	fclose(f);
	
	int p = 0;
	while(directory[p] != NULL) p++;
	directory[p] = (char*)malloc(13);
	directory_original[p] = (char*)malloc(13);
	strcpy(directory[p], filename);
	strcpy(directory_original[p], filename);
	opened_files[p] = fopen(filename, "r+");
	
	z80_set_high(cpu, regAF, 0x00); // success
}

/* Entered with C=17h, DE=address of FCB. Returns error codes in BA and HL. */
void rename_file(Z80EX_CONTEXT *cpu, Z80EX_BYTE c, Z80EX_WORD de)
{
	file_control_block* fcb = (file_control_block*)(&memory[de]);
	int p = get_file_directory_position(fcb);
	if (p != -1)
	{
		char filename[13], new_filename[13];
		get_file_name(fcb, filename);
		get_file_name((file_control_block*)(&memory[de + 0x16]), filename);

		long pos = -1;
		
		if (opened_files[p] != 0)
		{
			// storing position
			fflush(opened_files[p]);
			pos = ftell(opened_files[p]);
			fclose(opened_files[p]);
			opened_files[p] = 0;
		}
		
		rename(filename, new_filename);

		free(directory[p]);
		free(directory_original[p]);
		directory[p] = (char*)malloc(13);
		directory_original[p] = (char*)malloc(13);
		strcpy(directory[p], new_filename);
		strcpy(directory_original[p], new_filename);
		
		if (pos != -1)
		{
			// restoring position
			opened_files[p] = fopen(new_filename, "r+");
			fseek(opened_files[p], pos, SEEK_SET);
		}
		
		z80_set_high(cpu, regAF, 0x00); // success
	}
	else
		z80_set_high(cpu, regAF, 0xFF); // error
}

/* Entered with C=18h. Returns bitmap in HL. */
void return_bitmap_of_logged_in_drives(Z80EX_CONTEXT *cpu, Z80EX_BYTE c, Z80EX_WORD de)
{
	z80ex_set_reg(cpu, regHL, 0x01);
}

/* Entered with C=19h. Returns drive in A. Returns currently selected drive. 0 => A:, 1 => B: etc. */
void return_current_drive(Z80EX_CONTEXT *cpu, Z80EX_BYTE c, Z80EX_WORD de)
{
	z80_set_high(cpu, regAF, 0x00); // drive A
}

/* Entered with C=1Ah, DE=address. */
void set_dma_address(Z80EX_CONTEXT *cpu, Z80EX_BYTE c, Z80EX_WORD de)
{
	dma_address = de;
}
